package com.pany.utils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.Query;
import javax.persistence.Transient;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.hibernate.EntityMode;
import org.hibernate.Session;

import play.db.jpa.JPABase;

import com.pany.domain.Condition;
import com.pany.domain.ConditionRequest;
import com.pany.domain.Operator;
import com.pany.domain.OrderField;

/**
 * 业务帮助类
 * 
 * @author 曾杰
 * @since 2012-10-27/V0.1
 */
public class ServiceUtils
{
    /**
     * 转换条件请求为一个JPA查询
     * 
     * @param request
     * @param columns
     * @return
     */
    public static <T> List<T> queryByCondition(ConditionRequest request,
            Class<T> clazz, String... columns)
    {
        StringBuilder builder = new StringBuilder();
        String alias = clazz.getSimpleName().toLowerCase().substring(0, 1);
        if (!ArrayUtils.isEmpty(columns))
        {
            builder.append("select ");
            for (int i = 0; i < columns.length; i++)
            {
                builder/* .append(alias).append(".") */.append(columns[i]);
                if (i != columns.length - 1)
                {
                    builder.append(",");
                }
            }
        }
        builder.append(" from ");
        builder.append(request.getEntityType().getSimpleName()).append(" as ")
                .append(alias);
        Object[] params = handleCondition(request.getConditions(), builder,
                request.getEntityType());
        handleOrder(builder, request.getOrderFields(), request.getEntityType());

        Query q = JPABase.em().createQuery(builder.toString());

        if (!ArrayUtils.isEmpty(params))
        {
            for (int i = 0; i < params.length; i++)
            {
                q.setParameter(i + 1, params[i]);
            }
        }
        if (request.getCount() <= 0)
        {
            request.setCount(200);
        }
        if (request.getPage() <= 0)
        {
            request.setPage(1);
        }
        q.setFirstResult(request.getCount() * (request.getPage() - 1));
        q.setMaxResults(request.getCount());
        List list = q.getResultList();
        if (CollectionUtils.isEmpty(list) || ArrayUtils.isEmpty(columns))
        {
            return list;
        }
        try
        {
            for (int i = 0; i < list.size(); i++)
            {
                Object t = clazz.newInstance();
                Object[] values = (Object[]) list.get(i);
                for (int j = 0; j < columns.length; j++)
                {
                    BeanUtils.setProperty(t, columns[j], values[j]);
                }
                list.set(i, t);
            }
        }
        catch (InstantiationException e)
        {
        }
        catch (IllegalAccessException e)
        {

        }
        catch (InvocationTargetException e)
        {
        }
        return list;
    }

    public static int queryCountByCondition(ConditionRequest request)
    {
        StringBuilder builder = new StringBuilder();
        builder.append("select count(*) from ");
        builder.append(request.getEntityType().getCanonicalName());
        Object[] params = handleCondition(request.getConditions(), builder,
                request.getEntityType());
        Query q = JPABase.em().createQuery(builder.toString());
        if (!ArrayUtils.isEmpty(params))
        {
            for (int i = 0; i < params.length; i++)
            {
                q.setParameter(i + 1, params[i]);
            }
        }
        int count = Integer.parseInt(q.getResultList().get(0).toString());
        handlePagination(request, count);
        return count;
    }

    private static Object[] handleCondition(List<Condition> list,
            StringBuilder builder, Class<?> clazz)
    {
        if (CollectionUtils.isEmpty(list))
        {
            return null;
        }
        List<Object> params = new ArrayList<Object>(list.size());
        SimpleDateFormat format = null;
        boolean aped = false;
        for (Condition c : list)
        {
            if (StringUtils.isEmpty(c.getKey()))
            {
                continue;
            }
            if (c.getValue() == null)
            {
                continue;
            }
            Field f = getField(clazz, c.getKey());
            if (f == null)
            {
                continue;
            }
            if (!aped)
            {
                aped = true;
                builder.append(" where 1=1 ");
            }
            builder.append(" AND ");
            builder.append(f.getName());
            builder.append(c.getOpe());
            builder.append("?");
            if (f.getType() == String.class)
            {
                if (c.getOpe() == Operator.FUZZY)
                {
                    c.setValue("%" + c.getValue() + "%");
                }
            }
            else if (f.getType() == Timestamp.class
                    || f.getType() == Date.class)
            {
                if (format == null)
                {
                    format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
                }
                try
                {
                    c.setValue(format.parse(c.getValue().toString()));
                }
                catch (ParseException e)
                {
                }
            }
            else if (f.getType() == Boolean.class
                    || f.getType() == boolean.class)
            {
                c.setValue(Boolean.parseBoolean(c.getValue().toString()));
            }
            else if (f.getType() == Double.class || f.getType() == double.class
                    || f.getType() == Float.class || f.getType() == float.class)
            {
                c.setValue(Double.parseDouble(c.getValue().toString()));
            }
            else if (Number.class.isAssignableFrom(f.getType())
                    || f.getType().isPrimitive())
            {
                c.setValue(Long.parseLong(c.getValue().toString()));
            }
            params.add(c.getValue());
        }
        return params.toArray();
    }

    private static void handleOrder(StringBuilder builder,
            List<OrderField> fields, Class<?> clazz)
    {
        if (CollectionUtils.isEmpty(fields))
        {
            return;
        }
        boolean aped = false;
        for (int i = 0; i < fields.size(); i++)
        {
            OrderField orderField = fields.get(i);
            if (orderField == null)
            {
                continue;
            }
            if (StringUtils.isEmpty(orderField.getField()))
            {
                continue;
            }
            Field f = getField(clazz, orderField.getField());
            if (f == null)
            {
                continue;
            }
            if (!aped)
            {
                aped = true;
                builder.append(" order by ");
            }
            builder.append(f.getName());
            builder.append(" ").append(orderField.getMethod()).append(",");
        }
        if (builder.charAt(builder.length() - 1) == ',')
        {
            builder.deleteCharAt(builder.length() - 1);
        }
    }

    public static void handlePagination(ConditionRequest req, int totalNum)
    {
        if (req.getCount() <= 0)
        {
            req.setCount(10);
        }
        int pageCount = (totalNum - 1) / req.getCount() + 1;
        if (req.getPage() > pageCount)
        {
            req.setPage(pageCount);
        }
        if (req.getPage() <= 0)
        {
            req.setPage(1);
        }
    }

    private static Field getField(Class<?> clazz, String name)
    {
        Field[] fields = clazz.getDeclaredFields();
        if (ArrayUtils.isEmpty(fields))
        {
            return null;
        }
        for (Field field : fields)
        {
            if (field.getName().toLowerCase().equals(name.toLowerCase()))
            {
                if (field.isAnnotationPresent(Transient.class))
                {
                    return null;
                }
                return field;
            }
        }
        return null;
    }
}
